package nl.scalasim

import scala.actors.Actor

/** Base class for all messages in scalasim. */
abstract class Message

/** Ask a producer to produce goods. */
case class Produce extends Message

/** Inform a Harvester goods have been produced. */
case class Produced(name: String, amount: Int) extends Message

/** Ask an actor to stop running. */
case class Stop extends Message

/** Ask a Harvester to harvest goods. */
case class Harvest extends Message

/** Inform a Citizen that goods have been produced. */
case class Harvested(name: String, amount: Int) extends Message


/** Producer actor. */
class Producer(val name: String, val goods: String) extends Actor {
  def act {
    loop {
      react {
        case Produce => {
          val amount = 1
          println("[%s] Producing %d %s".format(name, amount, goods))
          reply(Produced(goods, amount))
        }
        case Stop => exit
        case msg => println("[%s] Ignoring %s".format(name, msg))
      }
    }
  }
}

/** Harvester actor. */
class Harvester(val name: String,
                val goods: String,
                val speed: Int,
                val capacity: Int) extends Actor {

  private var producer: Actor = _
  var stored = 0

  def connectTo(p: Actor): Harvester = {
    producer = p
    this
  }

  def act {
    var harvestCalls = 0

    loop {
      react {
        case Harvest if producer == null => {
          println("[%s] Not harvesting, no producer".format(name))
          reply(Harvested(goods, 0))
        }
        case Harvest if stored >= capacity => {
          println("[%s] Not harvesting, capacity reached".format(name))
          reply(Harvested(goods, 0))
        }
        case Harvest if (harvestCalls + 1 < speed) => {
          harvestCalls += 1
          println("[%s] Working...".format(name))
          reply(Harvested(goods, 0))
        }
        case Harvest => {
          val caller = sender
          harvestCalls = 0
          producer ! Produce
          react {
            case Produced(produced, amount) if produced != goods => {
              println("[%s] Ignoring %d %s".format(name, amount, produced))
              caller ! Harvested(goods, 0)
            }

            case Produced(produced, amount) => {
              stored = (stored + amount) min capacity
              println("[%s] Harvested %d %s".format(name, amount, goods))
              caller ! Harvested(goods, amount)
            }
          }
        }
        case Stop => exit()
        case msg => println("[%s] Ignoring %s".format(name, msg))
      }
    }
  }
}


object Simulation extends Application {
  val producer = new Producer("Candy repository", "candy")
  println("[Sim] Constructed a producer called %s that produces %s"
          .format(producer.name, producer.goods))

  producer start

  producer ! "hello world"
  producer !? Produce match {
    case Produced(goods, amount) =>
      println("[Sim] Harvested %d of type %s".format(amount, goods))
  }
  producer ! Stop
}